package com.froggyware.tadpole;

import com.froggyware.tadpole.core.*;
import com.froggyware.tadpole.core.impl.JsonDiskDataStore;
import org.apache.commons.jxpath.JXPathContext;
import org.apache.commons.jxpath.JXPathNotFoundException;
import org.apache.commons.lang.NotImplementedException;

import java.io.FileReader;
import java.io.IOException;
import java.util.*;

/**
 * Database Store instance
 * User: mst
 * Date: Sep 5, 2011
 * Time: 5:50:53 PM
 */
public class Tadpole {

    private static Tadpole instance = null;

    private DataStore dataStore;

    private Tadpole() {
    }

    public static Tadpole getInstance() {
        if (instance == null) {
            instance = new Tadpole();
            instance.init();
        }
        return instance;
    }

    public static void reboot() {
        instance = null;
    }

    private Properties props = null;

    // database internal workings
    private Serializer serializer = null;
    private IdentifierGenerator identifierGenerator = null;
    private List<Migration> migrations = new ArrayList<Migration>();
    private List<Indexer> indexers = new ArrayList<Indexer>();

    // TODO this should rather be a index what exists on the filesystem.
    private Map<String, List<TadObject>> collections = new HashMap<String, List<TadObject>>();

    // TODO implement this, there should be a cache, objectcount based.
    //private Stack<TadObject> cache = new Stack<TadObject>();

    public void init() {
        props = new Properties();
        try {
            props.load(new FileReader("tadpole.conf"));

        } catch (IOException e) {
            // TODO deal with this here.
        }

        // use classloader to instanciate all indexers, migrations etc. pp.
        dataStore = new JsonDiskDataStore();

        // synchronize with disk store
        for (String collectionName : dataStore.getCollectionNames()) {
            List<TadObject> objects = new ArrayList<TadObject>();
            for (String id : dataStore.getObjectIds(collectionName)) {
                objects.add(new DeflatedObject(id));
            }
            collections.put(collectionName, objects);
        }

    }

    /**
     * Determine which of the filestore implementation to use.  Default is the JsonDiskDataStore
     * @param store
     */
    public void setDataStore(DataStore store) {
        this.dataStore =store;
    }

    /**
     *
     * @return
     */
    public DataStore getDataStore() {
        if(dataStore ==null)
            dataStore = new JsonDiskDataStore();
        return dataStore;
    }

    /**
     * Returns the names of all collections known.
     * @return
     */
    public Set<String> getCollectionNames() {
        return collections.keySet();
    }

    public static <T extends TadObject> Result<T> find(Class collectionClass) {
        Tadpole pole = getInstance();
        List<TadObject> objects = pole.collections.get(pole.getCollectionName(collectionClass));
        Result<TadObject> result = pole.inflateObjects(collectionClass, objects);
        return (Result<T>) result;
    }

    private Result<TadObject> inflateObjects(Class collectionClass, List<TadObject> objects) {
        Result<TadObject> result = new ResultImpl<TadObject>();
        for(TadObject object : objects) {
            if( object instanceof DeflatedObject) {
                object = load(object.getId(), collectionClass);
            }
            result.add(object);
        }
        return result;
    }

    public static <T extends TadObject> T findById(Class collectionClass, String id) {
        Tadpole pole = getInstance();
        List<TadObject> collection = pole.collections.get(pole.getCollectionName(collectionClass));
        for(TadObject object : collection) {
            if (object.getId().equals(id)) {
                return (T) object;
            }
        }
        return null;
    }

    public static <T extends TadObject> T findOne(Class collectionClass, String query) {
        List<T> objects = find(collectionClass, query);
        if (objects.size() > 0) {
            return (T) objects.get(0);
        } else {
            return null;
        }
    }

    public static <T extends TadObject> Result<T> find(Class collectionClass, String query) {
        Tadpole pole = getInstance();
        List<TadObject> collection = pole.collections.get(pole.getCollectionName(collectionClass));
        Result<TadObject> result = new ResultImpl<TadObject>();
        if (collection != null) {
            for(TadObject object : collection) {
                if (object instanceof DeflatedObject) {
                    object = pole.load(object.getId(), collectionClass);
                }
                try {
                    JXPathContext   context = JXPathContext.newContext(object);
                    Object elem = context.getValue(query);
                    if (Boolean.TRUE.equals(elem))  {
                        result.add(object);
                    } else if (Boolean.FALSE.equals(elem)) {
                        continue;
                    } else if (elem instanceof List) {
                        result.addAll((List<T>)elem);
                    } else if (elem instanceof TadObject) {
                        result.add((TadObject) elem);
                    }
                } catch (JXPathNotFoundException junfe) {
                    // ignore, elemnt does not match. bad design....
                }
            }
        }
        return (Result<T>) result;
    }


    public static int count(String query) {
        return 0;
    }

    public int count() {
        // never throw any exception
        return 0;
    }

    public static <T extends TadObject> T load(String id) {
        Tadpole pole = getInstance();
        for(List<TadObject> objects : pole.collections.values()) {
            for (TadObject object : objects) {
                if (object.getId().equals(id)) {
                    return (T) object;
                }
            }
        }
        return null;
    }

    /**
     *
     * @param id
     * @param clazz
     * @param <T>
     * @return
     */
    public <T extends TadObject> T load(String id, Class clazz) {
        if(dataStore == null) {
            dataStore = new JsonDiskDataStore();
        }

        TadObject t = dataStore.read(id,clazz);
        return (T)t;
    }

    /**
     *
     * @param object
     */
    public void save(TadObject object) {

        if(dataStore == null) {
            dataStore = new JsonDiskDataStore();
        }

        if (object.getId() != null) {
            // just overwrite the json file atm.
            dataStore.write(object);
        } else {
            // This should generally give us a good unique ID for a single device
            // TODO: we should add the device ID or something, image if we had cloud replication service
            object.setId(UUID.randomUUID().toString());
            
            List<TadObject> objects = collections.get(getCollectionName(object.getClass()));
            if (objects != null) {
                objects.add(object);
            } else {
                objects = new ArrayList<TadObject>();
                objects.add(object);
                collections.put(object.getClass().getName(), objects);
            }
            dataStore.write(object);
        }
    }

    /**
     *
     * @param object
     * @return
     */
    public boolean delete(TadObject object) {
        List<TadObject> collection = collections.get(getCollectionName(object.getClass()));
        collection.remove(object);
        dataStore.delete(object.getId(), object.getClass());
        return true;
    }

    /**
     * Drop the complete data store from disk.
     * Calling this method will remove all your data.
     */
    public void drop() {
        dataStore.drop();
    }

    /**
     * 
     * @param entityClass
     * @return
     */
    private String getCollectionName(Class entityClass) {
        String name = entityClass.getName();
        return name.substring(name.lastIndexOf(".")+1, name.length());
    }

}

